﻿/**
 *
 */

package org.kolonitsky.alexey.data.parser
{
    import com.adobe.utils.DateUtil;

    import flash.net.*
	import flash.events.*

    import mx.utils.ObjectUtil;

    import org.kolonitsky.alexey.utils.*;
    import org.kolonitsky.alexey.utils.DateUtil;


    /**
	 *
	 *   @see http://rfc.net/rfc4180.html RFC4180
	 *   @langversion ActionScript 3.0
	 *   @tiptext
	 */
	public class CSV extends EventDispatcher
	{

        //---------------------------------------------------------------------
        // Properties
        //---------------------------------------------------------------------

        //-----------------------------
        // source
        //-----------------------------

        private var _source:String = "";

        public function get source():String
        {
            return _source;
        }


        //-----------------------------
        // fieldEnclosureToken
        //-----------------------------

        private var _fieldEnclosureToken:String = "\"";
		
		/**
		 *
		 */
		public function get fieldEnclosureToken() : String
		{
			return _fieldEnclosureToken
		}

        public function set fieldEnclosureToken( value : String ) : void
        {
            _fieldEnclosureToken = value
        }


        //-----------------------------
        // recordsetDelimiter
        //-----------------------------

        private var _recordsetDelimiter:String = "\n";
		
		/**
		 *
		 */
		public function get recordsetDelimiter() : String
		{
			return _recordsetDelimiter
		}

        public function set recordsetDelimiter( value : String ) : void
        {
            _recordsetDelimiter = value
        }
		

        //-----------------------------
        // embedHeader
        //-----------------------------

        private var _embededHeader:Boolean = false;

		/**
		 *
		 */
		public function get embededHeader() : Boolean
		{
			return _embededHeader
		}

        public function set embededHeader( value : Boolean ) : void
        {
            _embededHeader = value
        }
		

        //-----------------------------
        // header overwrite
        //-----------------------------

        private var _headerOverwrite:Boolean = false;

		/**
		 *
		 */
		public function get headerOverwrite()  : Boolean 
		{
			return _headerOverwrite
		}

        public function set headerOverwrite( value : Boolean ) : void
        {
            _headerOverwrite = value
        }


        //-----------------------------
        // field separator
        //-----------------------------

        private var _fieldSeperator:String = ",";

        /**
         *
         */
        public function get fieldSeperator() : String
        {
            return _fieldSeperator;
        }

        public function set fieldSeperator( value : String ) : void
        {
            _fieldSeperator = value
        }


        //-----------------------------
        // header
        //-----------------------------

        private var _header:Array = [];
		
		/**
         *
		 */
		public function get header() : Array 
		{
			return _header
		}

        public function set header(value:Array):void
        {
            if ((!embededHeader && !headerHasValues)
                || (!embededHeader && headerHasValues && headerOverwrite)
                || headerOverwrite)
                _header = value
        }


        //-----------------------------
        // header has value
        //-----------------------------

		/**
         *
		 */
		public function get headerHasValues ():Boolean
		{
			var result:Boolean = _header && _header.length > 0;
            return result;
		}
		

        //---------------------------------------------------------------------
        // Methods
        //---------------------------------------------------------------------

        //-----------------------------
        // Constructor
        //-----------------------------

        /**
         *
         */
        public function CSV(source:String="",
            fieldSeparator:String=",",
            fieldEnclosureToken:String="\"",
            recordsetDelimiter:String="\n")
        {
            _source = source;
            _fieldSeperator = fieldSeparator;
            _fieldEnclosureToken = fieldEnclosureToken;
            _recordsetDelimiter = recordsetDelimiter;

            embededHeader = true;
            headerOverwrite = false;
        }

		/**
         *  Decode string to 2D array. Each array is one line CSV file and
         *  its items is a cell in CSV file.
		 */
		public function decode(source:String=""):Array
		{
            _source = source || _source;
			var count:int = 0;
			var result:Array = [];
			var data:Array = _source.toString().split(recordsetDelimiter);
			for (var i:int = 0; i < data.length; i++)
			{
				if(count % 2 == 0)
					 result.push(data[ i ]);
				else
					 result[result.length - 1] += data[i];
				count += StringUtils.count(data[i], fieldEnclosureToken);
			}
			result = result.filter(isNotEmptyRecord);
			result.forEach(fieldDetection);
            _header = result.shift();
			recordSet = result;
            return result;
		}

		/**
         *
		 */
		public function stringify(data:*, map:Object=null):String
		{
            var result:String = "";

            if (data is Array)
            {
                if (headerHasValues)
                    result += header.join(fieldSeperator) + recordsetDelimiter;

                for each (var line:Object in data)
                    result += stringifyObject(line,  map) + recordsetDelimiter;
            }
            else if (data is Object)
            {
                result += stringifyObject(data,  map);
            }

            return result;
		}
        
        private function stringifyObject(object:Object, map:Object=null):String
        {
            var result : String = "";
            if (map)
            {

            }
            /*
            if ( dataHasValues )
                for each ( var recordset : Array in data )
                    result += recordset.join( fieldSeperator ) + recordsetDelimiter
            data = result;
            */
            return result;
        }

        /**
         * Convert CSV string to array of Array with fields.
         * @return
         */
        public function toArray():Array
        {
            return recordSet;
        }

        /**
         * Convert CSV string to array of Object.
         * @param map
         * @return
         */
        public function toObject(map:Object, itemClass:Class=null):Array
        {
            var result:Array = [];
            for each (var record:Array in recordSet)
            {
                var item:* = new itemClass();
                for (var i:int = 0; i < record.length; i++)
                {
                    var key:* = header[i] in map ? header[i] : i;
                    setField(item,  map[key] || header[i], record[i]);
                }
            }
            return [];
        }
		
		
		
		//---------------------------------------------------------------------
        //
        // Private logic
        //
		//---------------------------------------------------------------------

        private function setField(item:Object, key:String, value:String):void
        {
            if (item == null || value == null || value == "")
                return;

            if (!mx.utils.ObjectUtil.isDynamicObject(item) && !(key in item))
                return;
            
            if (item[key] is String)
                item[key] = value;
            else if (item[key] is int || item[key] is uint)
                item[key] = parseInt(value);
            else if (item[key] is Number)
                item[key] = parseFloat(value);
            else if (item[key] is Date)
                item[key] = com.adobe.utils.DateUtil.parseW3CDTF(value);
            else
                item[key] = value;
        }

        private var _sortField:*;
        private var _sortSequence:String;

        private var recordSet:Array = [];

		/**
         *  Convert record string to array of values
		 */
		private function fieldDetection(element:*, index:int, arr:Array):void
		{	
			var count:uint  = 0;
			var result:Array = [];
			var cells:Array = element.split(fieldSeperator);
			for (var i:int=0; i < cells.length; i++)
			{
                var cellValue:String = StringUtils.trim(cells[i], ['"', '\'']);
				if(count % 2 == 0)
					 result.push(cellValue);
				else
					 result[result.length - 1] += fieldSeperator + cellValue;
				count += StringUtils.count(cells[i], fieldEnclosureToken);
			}
			arr[index] = result
		}
		
		
		
		/**
		 */
		private function sort2DArray( a : Array, b : Array ) : Number
		{
			var n : int = 0
			var r : int = _sortSequence == 'ASC' ? -1 : 1;
			if ( String( a[ _sortField ] ) < String( b[ _sortField ]) )
				n = r
			else if ( String( a[ _sortField ] ) > String( b[ _sortField ] ) )
				n = -r
			else
				n = 0
			return n;
		}
		

		/**
         *
		 */
		private function isNotEmptyRecord(element:*, index:int, arr:Array):Boolean
		{
			return Boolean(StringUtils.trim(element, ['"']));
		}
	}
	
}